﻿using System;
using WebFormsMvp.Binder;
using Ninject.Core;
using Ninject.Core.Parameters;
using Ninject.Core.Binding.Syntax;
using Ninject.Core.Activation;

namespace WebFormsMvp.Contrib.Ninject
{
    /// <summary>
    /// A WebFormsMvp binder using Ninject as the IoC container
    /// </summary>
    public class MvpPresenterKernel : IPresenterFactory, IDisposable
    {
        readonly object registerLock = new object();

        /// <summary>
        /// Ninject kernel 
        /// </summary>
        protected readonly IKernel kernel;
        /// <summary>
        /// Ninject module
        /// </summary>
        protected readonly StandardModule module;

        /// <summary>
        /// Initializes a new instance of the <see cref="MvpPresenterKernel"/> class. Using <see cref="Ninject.Core.InlineModule"/> as the StandardModel instance
        /// </summary>
        public MvpPresenterKernel()
            : this(new InlineModule())
        {
        }
        /// <summary>
        /// Initializes a new instance of the <see cref="MvpPresenterKernel"/> class.
        /// </summary>
        /// <param name="module">The <see cref="Ninject.Core.StandardModule"/> to use within the presenter factory</param>
        public MvpPresenterKernel(StandardModule module)
        {
            this.module = module;
            this.kernel = new StandardKernel(module);
            this.SetupBindings();
        }
        /// <summary>
        /// Method where custom bind rules are to be defined. This operates before the IKernel is created so the Module is complete when passed to the kernel
        /// </summary>
        /// <example>
        ///     this.Bind&lt;IMyInstance&gt;().To&lt;MyInstance&gt;();
        /// </example>
        public virtual void SetupBindings() { }
        /// <summary>
        /// Defines a Ninject binding rule
        /// </summary>
        /// <param name="type">The type.</param>
        /// <returns></returns>
        public virtual IBindingTargetSyntax Bind(Type type)
        {
            return this.module.Bind(type);
        }
        /// <summary>
        /// Defines a Ninject binding rule
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <returns></returns>
        public virtual IBindingTargetSyntax Bind<T>()
        {
            return this.module.Bind<T>();
        }

        /// <summary>
        /// Gets an item
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <returns></returns>
        public virtual T Get<T>()
        {
            return this.kernel.Get<T>();
        }

        /// <summary>
        /// Gets an item using the specified context.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="context">The context.</param>
        /// <returns></returns>
        public virtual T Get<T>(IContext context)
        {
            return this.kernel.Get<T>(context);
        }

        /// <summary>
        /// Gets an item using the specified parameters.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="parameters">The parameters.</param>
        /// <returns></returns>
        public virtual T Get<T>(IParameterCollection parameters)
        {
            return this.kernel.Get<T>(parameters);
        }

        /// <summary>
        /// Gets the specified type.
        /// </summary>
        /// <param name="type">The type.</param>
        /// <returns></returns>
        public virtual object Get(Type type)
        {
            return this.kernel.Get(type);
        }

        /// <summary>
        /// Gets the specified type.
        /// </summary>
        /// <param name="type">The type.</param>
        /// <param name="context">The context.</param>
        /// <returns></returns>
        public virtual object Get(Type type, IContext context)
        {
            return this.kernel.Get(type, context);
        }

        /// <summary>
        /// Gets the specified type.
        /// </summary>
        /// <param name="type">The type.</param>
        /// <param name="parameters">The parameters.</param>
        /// <returns></returns>
        public virtual object Get(Type type, IParameterCollection parameters)
        {
            return this.kernel.Get(type, parameters);
        }

        #region IPresenterFactory Members

        /// <summary>
        /// Creates the specified presenter type.
        /// </summary>
        /// <param name="presenterType">Type of the presenter.</param>
        /// <param name="viewType">Type of the view.</param>
        /// <param name="viewInstance">The view instance.</param>
        /// <returns></returns>
        public virtual IPresenter Create(Type presenterType, Type viewType, IView viewInstance)
        {
            var parameters = new ParameterCollection();
            parameters.Add(new ConstructorArgumentParameter("view", value: viewInstance));

            return (IPresenter)this.Get(presenterType, parameters);
        }

        /// <summary>
        /// Releases the specified presenter.
        /// </summary>
        /// <param name="presenter">The presenter.</param>
        public virtual void Release(IPresenter presenter)
        {
            kernel.Release(presenter);
        }

        #endregion

        #region IDisposable Members

        /// <summary>
        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
        /// </summary>
        public void Dispose()
        {
            if (this.module != null)
            {
                this.module.Dispose();
            }

            if (this.kernel != null)
            {
                this.kernel.Dispose();
            }
        }

        #endregion
    }
}
